package arch;

import jason.asSyntax.Literal;
import jason.asSyntax.NumberTerm;
import jason.bb.BeliefBase;
import jason.environment.grid.Location;

import java.awt.Color;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.*;

import javax.imageio.ImageIO;

import jia.CowCluster;
import jia.Vec;
import env.WorldModel;

/**
 * Class used to model the scenario (for an agent view)
 * 
 * @author jomi
 */
public class LocalWorldModel extends WorldModel {

	int[][] visited; // count the visited locations
	int minVisited = 0; // min value for near least visited

	private Random random = new Random();

	Set<Vec> cows = new HashSet<Vec>();
	boolean isCowsUptodate = false;

	int[][] cowsrep; // cows repulsion
	int[][] agsrep; // agents repulsion
	int[][] obsrep; // obstacle repulsion
	int[][] enemycorralrep; // repulsion from enemy corral
	public int[][] agentVisits;

	public int lastRevisionStep = Integer.MIN_VALUE;
	public Location[] targets;
	public CowCluster[] clusters;
		
	public HashMap<Integer, Point> cowLocations;

	BeliefBase bb; // agent's BB
	
	
	// Scout only
	public Point scoutCurrentTarget;
	public int scoutStepsWithTarget = Integer.MAX_VALUE;
	public ArrayList<Point> scoutVisited = new ArrayList<Point>();

	// private Logger logger = Logger.getLogger("jasonTeamSimLocal.mas2j." +
	// LocalWorldModel.class.getName());

	public LocalWorldModel(int w, int h, int nbAg, BeliefBase bb) {
		super(w, h, nbAg);
		this.bb = bb;
		clusters = new CowCluster[0];
		
		visited = new int[getWidth()][getHeight()];
		for (int i = 0; i < getWidth(); i++)
			for (int j = 0; j < getHeight(); j++)
				visited[i][j] = 0;

		cowsrep = new int[getWidth()][getHeight()];

		agsrep = new int[getWidth()][getHeight()];
		obsrep = new int[getWidth()][getHeight()];
		enemycorralrep = new int[getWidth()][getHeight()];
		agentVisits = new int[getWidth()][getHeight()];
		for (int i = 0; i < getWidth(); i++)
			for (int j = 0; j < getHeight(); j++) {
				agsrep[i][j] = 0;
				obsrep[i][j] = 0;
				enemycorralrep[i][j] = 0;
				agentVisits[i][j] = 0;
			}

		targets = new Location[agsByTeam];
		cowLocations = new HashMap<Integer, Point>();
	}

	@Override
	public void setAgPos(int ag, int x, int y) {
		super.setAgPos(ag, x, y);
		increaseVisits(x, y, 8);
	}

	private void increaseVisits(int x, int y, int range) {
		for (int i = x - range; i < x + range; i++) {
			for (int j = y - range; j < y + range; j++) {
				if (i >= 0 && i < getWidth() && j >= 0 && j < getHeight())
					agentVisits[i][j]++;
			}
		}
	}

	public void createImage(String fileName) {

		BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);

		for (int x = 0; x < agentVisits.length; x++) {
			for (int y = 0; y < agentVisits[0].length; y++) {
				
				if (hasObject(WorldModel.AGENT, x, y)) {
					Location leader = getAgPos(0);
					Location scout = getAgPos(1);
					if(leader != null && leader.x == x && leader.y == y) 
						bi.setRGB(x, y, new Color(100, 0, 0).getRGB());
					else if (scout != null && scout.x == x && scout.y == y)
						bi.setRGB(x, y, new Color(200, 0, 0).getRGB());
					else
						bi.setRGB(x, y, new Color(255, 0, 0).getRGB());
				} else if (hasObject(WorldModel.TARGET, x, y)) {
					for(int i = 0; i < targets.length; i++) {
						if(targets[i] == null)
							continue;
						if(i == 1 && targets[i].x == x && targets[i].y == y) 
						{
							bi.setRGB(x, y, new Color(125, 125, 0).getRGB());
							//System.out.println("x"+targets[i].x +"y"+ targets[i].y +"agent"+ i);
						} 
						else if (targets[i].x == x && targets[i].y == y) 
						{
							bi.setRGB(x, y, new Color(255, 255, 0).getRGB());
							//System.out.println("x"+targets[i].x +"y"+ targets[i].y +"agent"+ i);
						}
					}
				} else if (hasObject(WorldModel.COW, x, y)) {
					bi.setRGB(x, y, new Color(0, 0, 255).getRGB());
				} else if (hasObject(WorldModel.SWITCH, x, y)) {
					bi.setRGB(x, y, new Color(0, 255, 0).getRGB());
				} else if (hasObject(WorldModel.FENCE, x, y)) {
					bi.setRGB(x, y, new Color(0, 255, 255).getRGB());
				} else if (hasObject(WorldModel.OBSTACLE, x, y)) {
					bi.setRGB(x, y, new Color(0, 0, 0).getRGB());
				} else if (hasObject(WorldModel.ENEMY, x, y)) {
					bi.setRGB(x, y, new Color(100, 100, 100).getRGB());
				}  else if (hasObject(WorldModel.CORRAL, x, y)) {
					bi.setRGB(x, y, new Color(255, 0, 255).getRGB());
				} else if (hasObject(WorldModel.ENEMYCORRAL, x, y)) {
					bi.setRGB(x, y, new Color(150, 0, 150).getRGB());
				} else {
					int p = (int) agentVisits[x][y];
					if (p > 200)
						bi.setRGB(x, y, new Color(55, 55, 55).getRGB());
					else if (p == 0) 
						bi.setRGB(x, y, new Color(255, 255, 255)
								.getRGB());
					else 
						bi.setRGB(x, y, new Color(255 - p, 255 - p, 255 - p)
						.getRGB());
				}
			}
		}
		
		for(CowCluster c : clusters) {
			bi.setRGB(c.x, c.y, new Color(0, 255, 0).getRGB());
		}

		try {
			ImageIO.write(bi, "png", new File(fileName));
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	@Override
	public void add(int value, int x, int y) {
		// ignore cows in corral
		if (hasObject(WorldModel.CORRAL, x, y) && value == WorldModel.COW)
			return;
		// if (value == WorldModel.AGENT || value == WorldModel.ENEMY) {
		if (value == WorldModel.ENEMY) {
			increp(agsrep, x, y, 2, 2);
		} else if (value == WorldModel.OBSTACLE) {
			increp(obsrep, x, y, 1, 1);
		} else if (value == WorldModel.ENEMYCORRAL) {
			increp(enemycorralrep, x, y, 3, 3);
			Point p = new Point();
			p.x = x;
			p.y = y;
			EnemyCorralPoint = p;
//			value = OBSTACLE;
		}
		super.add(value, x, y);
	}

	public void addCow(int x, int y, int id) {
		// System.out.println("Adding key: " + id);
		if (cowLocations.containsKey(id)) {
			// System.out.println("> Removing first.");
			Point oldLoc = cowLocations.get(id);
			remove(WorldModel.COW, oldLoc.x, oldLoc.y);
		}
		// System.out.println("> Adding " + x + ", " + y);
		cowLocations.put(id, new Point(x, y));
		add(WorldModel.COW, x, y);
	}

	@Override
	public void remove(int value, int x, int y) {
		super.remove(value, x, y);
		// if (value == WorldModel.AGENT || value == WorldModel.ENEMY) {
		if (value == WorldModel.ENEMY) {
			increp(agsrep, x, y, 2, -2);
		}
	}

	// cows methods

	public void clearCows() {
		isCowsUptodate = false;
	}

	public Set<Vec> getCows() {
		if (!isCowsUptodate)
			updateCowsFromBB();
		return cows;
	}

	private static final Literal cowLiteral = Literal
			.parseLiteral("cow(Id,X,Y)");

	private void updateCowsFromBB() {
		if (bb == null)
			return;

		// clean
		removeAll(WorldModel.COW);

		for (int i = 0; i < getWidth(); i++)
			for (int j = 0; j < getHeight(); j++)
				cowsrep[i][j] = 0;

		cows.clear();

		// rebuild
		Iterator<Literal> i = bb.getCandidateBeliefs(cowLiteral, null);
		if (i != null) {
			while (i.hasNext()) {
				Literal c = i.next();
				int x = (int) ((NumberTerm) c.getTerm(1)).solve();
				int y = (int) ((NumberTerm) c.getTerm(2)).solve();
				addCow(x, y);
			}
		}
		isCowsUptodate = true;
	}

	public void addCow(int x, int y) {
		add(WorldModel.COW, x, y);
		cows.add(new Vec(this, x, y));
		increp(cowsrep, x, y, 3, 1);
	}

	public int getCowsRep(int x, int y) {
		return cowsrep[x][y];
	}

	public int getAgsRep(int x, int y) {
		return agsrep[x][y];
	}

	public int getObsRep(int x, int y) {
		return obsrep[x][y];
	}

	public int getEnemyCorralRep(int x, int y) {
		return enemycorralrep[x][y];
	}

	private void increp(int[][] m, int x, int y, int maxr, int value) {
		for (int r = 1; r <= maxr; r++)
			for (int c = x - r; c <= x + r; c++)
				for (int l = y - r; l <= y + r; l++)
					if (inGrid(c, l)) {
						m[c][l] += value;
					}
	}

	// occupied means the places that can not be considered as nearFree
	public Location nearFree(Location l, List<Location> occupied)
			throws Exception {
		int w = 0;
		Location newl;
		if (occupied == null)
			occupied = Collections.emptyList();
		// List<Location> options = new ArrayList<Location>();
		while (true) {
			// options.clear();
			for (int y = l.y - w + 1; y < l.y + w; y++) {
				// System.out.println(" "+(l.x+w)+" "+y);
				// System.out.println(" "+(l.x-w)+" "+y);
				newl = new Location(l.x - w, y);
				if (isFree(newl) && !occupied.contains(newl))
					// options.add(newl);
					return newl;
				newl = new Location(l.x + w, y);
				if (isFree(newl) && !occupied.contains(newl))
					// options.add(newl);
					return newl;
			}
			for (int x = l.x - w; x <= l.x + w; x++) {
				newl = new Location(x, l.y - w);
				if (isFree(newl) && !occupied.contains(newl))
					// options.add(newl);
					return newl;
				newl = new Location(x, l.y + w);
				if (isFree(newl) && !occupied.contains(newl))
					// options.add(newl);
					return newl;
			}
			// if (!options.isEmpty())
			// return options.get(random.nextInt(options.size()));
			w++;
		}
	}

	public int countObjInArea(int obj, Location startPoint, int size) {
		int c = 0;
		for (int x = startPoint.x - size; x <= startPoint.x + size; x++) {
			for (int y = startPoint.y - size; y <= startPoint.y + size; y++) {
				if (hasObject(obj, x, y)) {
					c++;
				}
			}
		}
		return c;
	}

	public int getVisited(Location l) {
		return visited[l.x][l.y];
	}

	public void incVisited(Location l) {
		incVisited(l.x, l.y);
	}

	public void incVisited(int x, int y) {
		visited[x][y] += 2;
		increp(visited, x, y, 4, 1);
		/*
		 * if (x > 0) visited[x-1][y ]++; if (y > 0) visited[x ][y-1]++; if (y >
		 * 0 && x > 0) visited[x-1][y-1]++; if (y+1 < getHeight()) visited[x
		 * ][y+1]++; if (x > 0 && y+1 < getHeight()) visited[x-1][y+1]++; if
		 * (x+1 < getWidth()) visited[x+1][y ]++; if (x+1 < getWidth() && y > 0)
		 * visited[x+1][y-1]++; if (x+1 < getWidth() && y+1 < getHeight())
		 * visited[x+1][y+1]++;
		 */
	}

	/** returns the near location of x,y that was least visited */
	public Location getNearLeastVisited(Location agloc, Location tr, Location bl) {
		/*
		 * logger.info("------"); for (int i = 0; i < getWidth(); i++) { String
		 * line = ""; for (int j = 0; j < getHeight(); j++) { line +=
		 * visited[j][i] + " "; } logger.info(line); }
		 */
		Location better = null;

		// int visitedTarget = 0;
		int loopcount = 0;
		while (loopcount < 100) {
			loopcount++;

			int x = agloc.x;
			int y = agloc.y;
			int w = 1;
			int dx = 0;
			int dy = 0;
			int stage = 1;// (x % 2 == 0 ? 1 : 2);
			better = null;
			while (w < getWidth()) { // ( (w/2+distanceToBorder) < getWidth()) {

				switch (stage) {
				case 1:
					if (dx < w) {
						dx++;
						break;
					} else {
						stage = 2;
					}
				case 2:
					if (dy < w) {
						dy++;
						break;
					} else {
						stage = 3;
					}
				case 3:
					if (dx > 0) {
						dx--;
						break;
					} else {
						stage = 4;
					}
				case 4:
					if (dy > 0) {
						dy--;
						break;
					} else {
						stage = 1;
						x--;
						y--;
						w += 2;
					}
				}

				Location l = new Location(x + dx, y + dy);
				if (isFree(l) && !l.equals(agloc) && l.isInArea(tr, bl)) {
					if (visited[l.x][l.y] < minVisited) { // a place better then
															// minVisited! go
															// there
						return l;
					}
					if (visited[l.x][l.y] == minVisited) { // a place in the
															// minVisited level
						if (better == null) {
							better = l;
						} else if (l.distance(agloc) < better.distance(agloc)) {
							better = l;
						} else if (l.distance(agloc) == better.distance(agloc)
								&& random.nextBoolean()) { // to chose ramdomly
															// equal options
							better = l;
						}
					}
				}
			} // end while

			if (better != null) {
				return better;
			}
			minVisited++;
		}
		return better;
	}

	public int getTargetAgentAtPos(int x, int y) {
		for (int i = 0; i < targets.length; i++) {
			if (targets[i].x == x && targets[i].y == y) {
				return i;
			}
		}
		return -1;
	}

	public Location getTargetAgentPos(int agent) {
		try {
			if (targets[agent].x == -1)
				return null;
			else
				return (Location) targets[agent].clone();
		} catch (Exception e) {
			return null;
		}
	}

	public void setTargetAgentPos(int agent, int x, int y) {
		Location oldLoc = getTargetAgentPos(agent);
		
		if (oldLoc != null && oldLoc.x >= 0 && oldLoc.y >= 0
				&& oldLoc.x < getWidth() && oldLoc.y < getHeight()) {
			remove(TARGET, oldLoc.x, oldLoc.y);
		}
		targets[agent] = new Location(x, y);
		if (x >= 0 && y >= 0 && x < getWidth() && y < getHeight()) {
			add(TARGET, x, y);
		}
	}

	/** removes enemies/gold around l */
	public void clearAgView(Location l) {
		clearAgView(l.x, l.y);
	}

	private static final int cleanPerception = ~(ENEMY + COW);

	/** removes enemies/gold around x,y */
	public void clearAgView(int x, int y) {
		int r = agPerceptionRatio;
		for (int c = x - r; c <= x + r; c++) {
			for (int l = y - r; l <= y + r; l++) {
				if (inGrid(c, l)) {
					data[c][l] &= cleanPerception;
					if (view != null)
						view.update(c, l);
				}
			}
		}
	}

}
